﻿using Size_W = System.Int64;

namespace JiShiBen.Core.DataStructure
{
	public class SpanTable : DoublyLinkedList<Span>
	{
		#region Constructor

		public SpanTable()
		{
		}

		#endregion


		#region Private Members

		#region Methods

		private void InsertSpanBegin(Span target, Span span)
		{
			if (target == First)
				InsertBegin(span);
			else
			{
				var prev = target.Previous;
				if (prev.Buffer == span.Buffer && prev.LastIndex + 1 == span.Offset)	//	continuous insert
					prev.Length += span.Length;
				else
					InsertBefore(target, span);
			}
		}

		private void InsertSpanMiddle(Span target, Span span, Size_W insertAt)
		{
			var before = new Span(this, target.Buffer, target.Offset, insertAt - target.Offset) {Previous = target.Previous};
			target.Previous.Next = before;
			before.Next = span;
			span.Previous = before;

			var after = new Span(this, target.Buffer, insertAt - target.Offset, target.LastIndex - insertAt) {Previous = span};
			span.Next = after;
			after.Next = target.Next;
			target.Next.Previous = after;
		}

		private Span FindSpanByOffset(Size_W offset, out Size_W adjustedOffset)
		{
			adjustedOffset = offset;

			if (IsEmpty)
				return null;

			var temp = First;
			Size_W counted = 0;
			while (temp != null)
			{
				if (temp.Length + counted > offset)
					break;

				counted += temp.Length;
				temp = temp.Next;
			}

			if (temp != null)
				adjustedOffset = temp.Offset + (offset - counted);

			return temp;
		}

		private void RemoveSpanAtOffset(Span target, long adjustedOffset, long length)
		{
			if (length <= 0) return;

			var lengthExceedSpan = adjustedOffset + length > target.LastIndex;

			if (adjustedOffset == target.Offset)
			{
				if (lengthExceedSpan)
				{
					length -= target.Length;
					var next = target.Next;
					base.Remove(target);
					RemoveSpanAtOffset(next, next.Offset, length);
					return;
				}

				target.Length -= length;
				target.Offset += length;
			}
			else
			{
				var before = new Span(this, target.Buffer, target.Offset, adjustedOffset - target.Offset);
				var after = new Span(this, target.Buffer, adjustedOffset + length, target.LastIndex - (adjustedOffset + length));
				before.Previous = target.Previous;
				if (target != First)
					target.Previous.Next = before;
				before.Next = after;
				after.Previous = before;
				after.Next = target.Next;
				if (target != Last)
					target.Next.Previous = after;
			}
		}

		#endregion

		#endregion


		#region Public Members

		#region Properties

		public Span First
		{
			get { return (Span)GetFirst(); }
		}

		public Span Last
		{
			get { return (Span)GetLast(); }
		}

		//public int Count { get; private set; }

		//public Size_W Length { get; private set; }

		#endregion

		#region Methods

		public void Initialize(Span head)
		{
			InsertBegin(head);
		}

		public void Insert(int bufferId, Size_W offset, Size_W globalOffset, Size_W length)
		{
			long adjustedOffset;
			var span = new Span(this, bufferId, offset, length);
			var target = FindSpanByOffset(globalOffset, out adjustedOffset);

			if (target == null)
				InsertBegin(span);
			else if (target.Offset == adjustedOffset)
				InsertSpanBegin(target, span);	//	Inserting at beginning of span
			else
				InsertSpanMiddle(target, span, adjustedOffset);
		}

		public void Remove(Size_W globalOffset, Size_W length)
		{
			long adjustedOffset;
			var span = FindSpanByOffset(globalOffset, out adjustedOffset);
			if (span == null) return;

			RemoveSpanAtOffset(span, adjustedOffset, length);
		}

		#endregion

		#endregion

		public override void Clear()
		{
			lock (_syncObject)
			{
				var temp = Last;
				while (temp != First)
				{
					temp = temp.Previous;
					temp.Next.Dispose();
					Remove(temp.Next);
				}

				Remove(First);
			}
		}
	}
}
